import { Box2, Group, Object3D, Vector2, Vector3 } from "three";
import GeoUtils from "../utils/GeoUtils";
import { App, Point } from "../Engine";
import Helper from "../utils/Helper";
import TaskManager from "../utils/TaskManager";
import BaseTile from "./Tile/BaseTile";
import DemTile from "./Tile/DemTile";
import WMSTile from "./Tile/WMSTile";
import { LRUMap } from 'lru_map';
import { ShapeObject } from "../component/models/ShapeObject";
import Const from "../application/constants/Const";
import Events from "../application/constants/Events";
import { TileSwitchController } from "../processor/ZoomProcessor/TileSwitchController";
import MeshUtil from "../renderExtension/utils/MeshUtil";
import ParseBundleTile from "./utils/ParseBundleTile";
import ParseSatelliteConfig from "./utils/ParseSatelliteConfig";
import Config from "../application/constants/Config";
/**
 * 底图图层，该图层是瓦片的容器类
 */
class BaseTileMap extends Group {
    public tileType: string;
    public options: any;
    protected cache: any;
    protected oldTiles: Array<string> = [];
    protected addTiles: Array<string>;
    protected delTiles: Map<string, Array<any>>;
    protected keepTiles: Map<string, Array<any>>;
    protected childrenTileNodes: Map<string, Array<any>>;//和layer.children中挂接节点保持一致
    protected requestCount: number;//请求计数，当该数值与addTiles中请求个数相同时，即全部请求都返回时，执行更新图层节点操作
    private tm;
    private app: App;
    private tileRange: { minCol: number, maxCol: number, minRow: number, maxRow: number, zoom: number };//每次渲染时要加载的瓦片范围
    private mapRange: {leftDownPoint: Point, rightUpPoint: Point}; //当前地图的最大范围
    private clapData: { isOpenClap: boolean, clapPointArray: [], clapPointCount: number, renderSide: number };
    private configParser: ParseSatelliteConfig;
    private parseBundle: ParseBundleTile;
    public falldownSurface: Object3D;

    /**
     * 最高数据层级
     */
    public maxDataLevel: number = 18;

    /**
     * true表示该图层不参与射线拾取，false表示图层参与射线拾取
     */
    public canNotSelect: boolean;
    private switchController: TileSwitchController;
    private lastZoom = -1;
    private lastCameraPosition: Vector3;
    private isLoadedConfig = false; //是否加载了配置文件
    public mapOrder: number = 0;
    
    /**
     * 底图图层的构造函数
     * @param options 构造函数需要用到的参数
     * @param options.url 影像底图的url
     * @param options.urlDecimal 影像底图url中的瓦片行列号是否是十进制，true表示十进制，false表示十六进制
     * @param options.wmsUrl WMS的url
     * @param options.wmsUrlDecimal WMS的url中的瓦片行列号是否是十进制，true表示十进制，false表示十六进制     * 
     * @param options.demUrl dem底图的url
     * @param options.demUrlDecimal dem底图url中的瓦片行列号是否是十进制，true表示十进制，false表示十六进制
     * @param options.demHeightAdjust 一个高度值，用于调整dem底图的高度
     * @param options.useDefaultTexture 是否使用默认瓦片底图，当瓦片的影像纹理请求还没有成功的时候，瓦片先使用默认瓦片底图
     * @param options.zIndex 为了避免深度冲突，调整高度值, 默认值为1
     * @param options.demScale dem高度的夸张值
     * @param options.mapOrdre 渲染的顺序
     */
    constructor(options) {
        super();
        this.options = options;
        this.mapOrder = options.mapOrder || Const.RenderOrder.Map;
        this.app = App.getInstance();
        this.cache = new LRUMap(1);
        this.oldTiles = new Array<string>();
        this.addTiles = new Array<string>();
        this.delTiles = new Map<string, Array<any>>();
        this.keepTiles = new Map<string, Array<any>>();
        this.childrenTileNodes = new Map<string, Array<any>>();
        this.requestCount = 0;
        this.canNotSelect = (options.canNotSelect === undefined) ? false : options.canNotSelect;
        this.maxDataLevel = options.maxDataLevel ? options.maxDataLevel : 18;

        this.tileType = this.options.tileType;

        this.setMapRange();

        this.tm = new TaskManager({
            excuteByBatch: true
        });
        this.switchController = new TileSwitchController(this);
        // this.curZoom = this.app.map.zoom;

        let configUrl = this.options.configUrl;
        let bundleUrl = this.options.bundleUrl;
        if(configUrl){ //加载专门的地图配置文件
            this.configParser = new ParseSatelliteConfig();

            if(bundleUrl){
                this.parseBundle = new ParseBundleTile(bundleUrl, { packSize: 128 });
            }
            
            let self = this;
            this.configParser.loadJson(configUrl, function () {
                self.isLoadedConfig = true;
            });
        }else{
            this.isLoadedConfig = true;
        }

        if(this.options.visible == false){
            this.visible = false;
        }else{
            this.visible = true;
        }
        this.name = options.name;
    }

    private setMapRange(){
        if(this.options.mapRange && this.options.mapRange.leftDownPoint && this.options.mapRange.rightUpPoint){
            let minX = Math.min(this.options.mapRange.leftDownPoint.x, this.options.mapRange.rightUpPoint.x);
            let maxX = Math.max(this.options.mapRange.leftDownPoint.x, this.options.mapRange.rightUpPoint.x);

            let minY = Math.min(this.options.mapRange.leftDownPoint.y, this.options.mapRange.rightUpPoint.y);
            let maxY = Math.max(this.options.mapRange.leftDownPoint.y, this.options.mapRange.rightUpPoint.y);

            this.mapRange = {
                leftDownPoint: new Point(minX, minY, 0, this.options.mapRange.leftDownPoint.EPSGType).toEPSGWeb(),
                rightUpPoint: new Point(maxX, maxY, 0, this.options.mapRange.leftDownPoint.EPSGType).toEPSGWeb()
            };
        }
    }

    checkZoomChange(zoom){
        let checkResult = {isSwitcher:false, isMoveDown: false};
        let cameraPositon = this.app.three.camera.position;

        if(this.lastZoom == -1){
            this.lastZoom = zoom;
            this.lastCameraPosition = cameraPositon.clone();
            return checkResult;
        }

        if(this.lastZoom != zoom){ //地图层级发生了变化
            this.lastZoom = zoom;

            checkResult.isSwitcher = true;
            if(cameraPositon.z > this.lastCameraPosition.z){ //相机往上移动
                checkResult.isMoveDown = false;
            }else if(cameraPositon.z < this.lastCameraPosition.z){ //相机往下移动
                checkResult.isMoveDown = true;
            }
        }
        this.lastCameraPosition = cameraPositon.clone();
        return checkResult;
    }

    /**加载图层数据 */
    public load(frustumBound: Array<Vector3>) {
        if(!this.isLoadedConfig){ //配置文件还没有加载完毕, 延时200毫秒后加载瓦片
            let self = this;
            setTimeout(() => {
                self.load(frustumBound);
            }, 200);
            return false;
        }

        if(!this.app.map){
            return false;
        }
        
        let zoom = Math.min(this.app.map.zoom, this.maxDataLevel);
        if (zoom > this.app.options.Map.maxZoom || zoom < this.app.options.Map.minZoom) {
           return false;
        }

        if(this.options.minDataLevel && zoom <= this.options.minDataLevel ){ //不支持地图的层级
            this.removeAllTiles();
            return false;
        }
        if(!this.isInsideMapRange(frustumBound)){ //不在地图范围内
            this.removeAllTiles();
            return false;
        }
        // let zoom = this.app.map.zoom;
        let newTiles = this.getTiles(frustumBound);
        let checkResult = this.checkZoomChange(zoom)

        if(checkResult.isSwitcher == true){ //层级切换的时候，走另外一套代码逻辑
            this.oldTiles = this.switchController.switch(checkResult.isMoveDown, this.tileRange);
        }
        
        let cachedTiles = new Array<string>();
        this.oldTiles.forEach((tile) => {
            if (newTiles.indexOf(tile) != -1) { //旧瓦片在新瓦片数组中存在，该瓦片继续保留
                this.keepTiles.set(tile, this.childrenTileNodes.get(tile));
                cachedTiles.push(tile);
            }else{ //旧瓦片在新瓦片数组中不存在，该瓦片需要被删除
                let deleteTile = this.childrenTileNodes.get(tile);
                this.delTiles.set(tile, deleteTile);
            }
        }, this);

        newTiles.forEach((tile) => {
            if (this.oldTiles.indexOf(tile) == -1) { //新的瓦片在旧瓦片数组中不存在，创建这个新的瓦片
                this.addTiles.push(tile);
            }
        }, this);
        //由于启用了批处理机制，所以oldTiles只保留之前已缓存过的数据也就是keepTiles，其余的数据在TaskManager处理后再加入到oldTiles
        this.oldTiles = cachedTiles;

        //update的函数频率触发较高，所以对上个update还未执行完的任务执行中断处理，保证不会因为异步任务回调引起节点树混乱，且节约网络资源
        if (this.tm && this.tm.isRunning)
            this.tm.abort();
        //重新初始化一个任务管理器，并将excuteByBatch属性设置为true，这样任务管理器内部会根据任务优先级分批次处理
        //excuteByBatch为false时，仍然按照优先级处理，但不会考虑异步任务的回调函数执行情况
        this.tm = new TaskManager({
            excuteByBatch: true
        });

        this.update(undefined);
        return true;
    }

    private isInsideMapRange(frustumBound){
        if(!this.mapRange){
            return true;
        }

        if(this.mapRange){
            let minBoundX = Infinity, maxBoundX = -Infinity, minBoundY = Infinity, maxBoundY = -Infinity;
            frustumBound.forEach(element => {
                minBoundX = Math.min(minBoundX, element.x);
                maxBoundX = Math.max(maxBoundX, element.x);
                minBoundY = Math.min(minBoundY, element.y);
                maxBoundY = Math.max(maxBoundY, element.y);
            });
            let mapRangeBox: Box2 = new Box2(new Vector2(this.mapRange.leftDownPoint.x, this.mapRange.leftDownPoint.y), new Vector2(this.mapRange.rightUpPoint.x, this.mapRange.rightUpPoint.y));
            let frustumBox: Box2 = new Box2(new Vector2(minBoundX, minBoundY), new Vector2(maxBoundX, maxBoundY));
            if(!mapRangeBox.intersectsBox(frustumBox)){
                return false;
            }
            return true;
        }
        return true;
    }


    /** 获取当前图层所有切片号 */
    public getTiles(frustumBound: Array<Vector3>): Array<string> {
        this.tileRange = GeoUtils.computeTileRange(frustumBound, Math.min(this.app.map.zoom, this.maxDataLevel));//根据相交点，计算要加载瓦片的范围
        let rc = new Array<string>();
        let cameraPositon = this.app.three.camera.position.clone();
        rc = Helper.visitTileFromNearToFar(this.tileRange, cameraPositon);

        return rc;
    }

    /** 更新渲染数据对象 */
    private update(data: any) {
        this.childrenTileNodes.clear();

        let _this = this;
        let priorityValue = 0;
        let tempIndex = 0;
        let mapLayCount = this.app.map.availableBaseMapNum;
        let maxLoadCount = Math.max(Math.ceil(Config.Map.patchLoadCount / mapLayCount), 1);
        this.addTiles.forEach((tile) => {
            // let tl = JSON.parse(tile);
            tempIndex++;
            if(tempIndex % maxLoadCount == 0){
                priorityValue++;
            }
           
            //task支持根据id查询任务状态，此场景不需要这个功能，所以没有给task设置id
            let task = {
                //通过优先级参数实现的节流机制 缓存瓦片5个为一批次，全部加载完成后再进行下一次批处理
                //priority为任务优先级，这个值越小，任务优先级越高，最小为0。若TaskManager设置为根据批次执行，则会根据优先级分组
                priority: priorityValue,//(1000 + Math.floor((++n) / 5)), //!tl.isTransformTile ? 0 : (1000 + Math.floor((++n) / 5)),
                //异步任务必须将这个值设置为true
                asyn: true,
                //complete属性原则上来讲不需要设置，但task没有写成类，为了保证编译通过，所以设置为空函数，在taskmananger内部，会对complete重写
                complete: function () { },
                //excute为任务执行时的具体操作
                excute: function () {
                    _this.oldTiles.push(tile);
                    _this.loadTile(tile, () => {
                        //所有的异步函数执行完成后都要执行task.complete()函数，是否执行过complete函数是任务管理器判断异步任务的回调函数是否执行的唯一依据
                        //complete可以返回一个布尔值，用于表明回调函数触发时，任务管理器是否被中断,false为任务管理器被中断
                        task.complete();
                    });
                },
            }
            this.tm.add(task);
        });

        this.tm.run();

        this.keepTiles.forEach((value, key) => {
            this.childrenTileNodes.set(key, value);
        }, this);

        this.delTiles.forEach((value) => {
            if (undefined !== value && value.length > 0) {
                value.forEach(element => {
                    if (element.dispose) {
                        element.dispose();
                    }
                    _this.remove(element);
                });
            }
        }, this);

        this.addTiles.splice(0, this.addTiles.length);
        this.delTiles.clear();
        this.keepTiles.clear();

        this.removeInvalidTiles();
    }

    private removeAllTiles(){
         let self = this;
        let removeArray = [];
        this.children.forEach(element => {
            removeArray.push(element);
        });

        removeArray.forEach(element => {
             MeshUtil.dispose(element);
        });
    }

    private removeInvalidTiles(){ //删除不在可视范围内的瓦片       
        let self = this;
        let removeArray = [];
        this.children.forEach(element => {
            if(!element['options']){
                return;
            }
            let lrc = element['options'].lrc;
            if(lrc.z == this.tileRange.zoom){
                if(lrc.x < self.tileRange.minCol){
                    removeArray.push(element);
                }else if(lrc.x > self.tileRange.maxCol){
                    removeArray.push(element);
                }else if(lrc.y < self.tileRange.minRow){
                    removeArray.push(element);
                }else if(lrc.y > self.tileRange.maxRow){
                    removeArray.push(element);
                }
            }

        });

        removeArray.forEach(element => {
            element.dispose();
 			self.remove(element);
        });
    }

    public removeTileNoCurZoom() {
        let self = this;
        let removeArray = [];
        this.children.forEach(element => {
            let lrc = element['options'].lrc;
            if(Math.abs(lrc.z - self.tileRange.zoom) > 0){
                removeArray.push(element);
            }
        });

        removeArray.forEach(element => {
            element.dispose();
 			self.remove(element);
        });
    }

    public loadTile(tile, success?) {
        // let tl = JSON.parse(tile);
        // let url = Helper.getTileUrl(this.options.url, tl.x, tl.y, tl.z, (this.options.urlDecimal === undefined) ? true : this.options.urlDecimal);
        // let demUrl = Helper.getTileUrl(this.options.demUrl, tl.x, tl.y, tl.z, (this.options.urlDecimal === undefined) ? true : this.options.urlDecimal);
        let tileJson = tile;
        if(typeof tile === 'string'){
            tileJson = JSON.parse(tile);
        }
        let tileRowCount = Math.pow(2, this.app.map.zoom); //当前层级横向和纵向瓦片的数量

        if ((tileJson.y >= tileRowCount) || (tileJson.y < 0)) {
            return null;
        }

        let config = {
            lrc: tileJson,
            // url: url,
            // demUrl: demUrl,
            url: this.options.url,
            parseBundle: this.parseBundle,
            urlDecimal: this.options.urlDecimal,                
            wmsUrl: this.options.wmsUrl,
            wmsUrlDecimal: this.options.wmsUrlDecimal,
            demUrl: this.options.demUrl,
            demUrlDecimal: this.options.demUrlDecimal,
            demHeightAdjust: this.options.demHeightAdjust,
            srcUrl: this.options.srcUrl,
            srcUse: this.options.srcUse,
            useDefaultTexture: this.app.options.Map.useDefaultTexture,
            zIndex: this.options.zIndex ? this.options.zIndex : 1,
            curzoom: Math.min(this.app.map.zoom, this.maxDataLevel),
            zooms: [this.app.options.Map.minZoom, this.app.options.Map.maxZoom],
            demScale: this.options.demScale ? this.options.demScale : 1,
            merge: (this.options.merge === undefined) ? true : this.options.merge,
            isAdditionalLayer: this.options.isAdditionalLayer,
            colorSpace: (this.options.colorSpace === undefined) ? '' : this.options.colorSpace,
            loaded: function (model) {
            if (success instanceof Function) success(model);
            }
        };
        //把config中没有的属性，从this.options中写入到config中
        config = { ...this.options, ...config };

        return this.getTile(config, tile); //这里一定要返回，否则会出内存泄露
    }

    private getTile(config, tile){
        let tileModel;
        var preLoadTileEvent = { type: 'preLoadTile', message: config };
        //@ts-ignore
        this.dispatchEvent(preLoadTileEvent);

        switch (this.options.tileType) {
            case Const.tileType.imageType:
                tileModel = new BaseTile(config);
                tileModel.renderOrder = this.mapOrder;
                break;
            case Const.tileType.demType:
                tileModel = new DemTile(config);
                tileModel.renderOrder = this.mapOrder;
                break;
            case Const.tileType.wmsType:
                tileModel = new WMSTile(config);
                tileModel.renderOrder = this.mapOrder;
                break;
            case Const.tileType.shapeType:
                if(this.app.map.zoom >= this.options.shapeMinLevel){
                    config.url = Helper.getTileUrl(this.options.url, config.lrc.x, config.lrc.y, config.lrc.z);
                    tileModel = new ShapeObject(config);
                    tileModel.falldownSurface = this.falldownSurface;
                }
                break;
        }

        if(tileModel){
            if (this.app.options.Render.shadowEnable) {
                tileModel.material.receiveShadow = this.app.options.Render.shadowEnable;
            }
            this.add(tileModel);
    
            this.childrenTileNodes.set(tile, [tileModel]);
    
            var loadTileEvent = { type: 'afterLoadTile', message: tileModel };
            //@ts-ignore
            this.dispatchEvent(loadTileEvent);
        }
       return tileModel;
    }

    /**
     * 该方法只对shape数据的建筑图层有效，用于设置建筑图层不同的颜色划分楼层
     * @param floorDivisionArray 楼层划分数组，该数组中每个元素的类型为： 
                {
                    floorRange: {startLevel: 1, endLevel: 5}, //设置楼层数范围
                    floorColor: '#FFB6C1' //设置楼层数范围的颜色
                }
     * example:
                var buildingLayer = app.map.getTileMap('shape', 'buildingLayer')
                buildingLayer.setBuildingFloorRange( [ //只有建筑图层才有该属性
                    {
                        floorRange: {startLevel: 1, endLevel: 5}, //设置楼层数范围
                        floorColor: '#FF4500' //设置楼层数范围的颜色
                    },
                    {
                        floorRange: {startLevel: 6, endLevel: 10}, //设置楼层数范围
                        floorColor: '#8B4513' //设置楼层数范围的颜色
                    },
                    {
                        floorRange: {startLevel: 11, endLevel: 15}, //设置楼层数范围
                        floorColor: '#CD853F' //设置楼层数范围的颜色
                    },
                    {
                        floorRange: {startLevel: 16, endLevel: 20}, //设置楼层数范围
                        floorColor: '#FAEBD7' //设置楼层数范围的颜色
                    },
                    {
                        floorRange: {startLevel: 21, endLevel: 100}, //设置楼层数范围
                        floorColor: '#FFF8DC' //设置楼层数范围的颜色
                    }
                ])
     */
    public setBuildingFloorRange(floorDivisionArray: Array<{floorRange: {startLevel: number, endLevel: number}, floorColor: string}>){
        this.options.floorDivisionArray = floorDivisionArray;
        this.app.options.ShapeConfig.floorDivisionArray = floorDivisionArray;
        
        this.children.forEach(element => {
            if(element instanceof ShapeObject){
                element.setAllBuildingFloorRange(floorDivisionArray);
            }
        });
    }

    /**
     * 清除该底图图层中的所有瓦片
     */
    public dispose() {
        this.clear();
        this.cache.clear();
        this.oldTiles = [];
        this.addTiles.splice(0, this.addTiles.length);
        this.delTiles.clear();
        this.keepTiles.clear();
        this.childrenTileNodes.clear();
        this.requestCount = 0;
    }
    public clearCache() {
        this.cache.clear();
    }
}
export { BaseTileMap }